package com.axzhengxin.platform.common.util;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * HttpClientUtil
 * apache http client工具类
 *
 * @author jiangnan
 * @date 2021/2/23
 */
public class HttpClientUtil {

    /**
     * 请求连接超时时间
     */
    private static final int CONNECTION_REQUEST_TIMEOUT = 30 * 1000;

    /**
     * 连接超时时间
     */
    private static final int CONNECT_TIMEOUT = 30 * 1000;

    /**
     * 套接字超时时间
     */
    private static final int SOCKET_TIMEOUT = 30 * 1000;

    /**
     * 最大连接数
     */
    private static final int MAX_TOTAL = 200;

    /**
     * 每路由最大连接数
     */
    private static final int MAX_PER_ROUTE = 50;

    private static void config(HttpRequestBase httpRequestBase) {
        // 配置请求的超时设置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
                .setConnectTimeout(CONNECT_TIMEOUT)
                .setSocketTimeout(SOCKET_TIMEOUT).build();
        httpRequestBase.setConfig(requestConfig);
    }

    /**
     * 获取HttpClient对象，枚举单例模式
     *
     * @return 客户端单例
     * @author xchen
     */
    private static CloseableHttpClient getHttpClient() {
        return HttpClientHolder.INSTANCE.getClient();
    }

    /**
     * 创建HttpClient对象
     *
     * @return 客户端对象
     * @author xchen
     */
    private static CloseableHttpClient createHttpClient() {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory
                .getSocketFactory();
        LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory
                .getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder
                .<ConnectionSocketFactory>create().register("http", plainsf)
                .register("https", sslsf).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
                registry);
        // 将最大连接数增加
        cm.setMaxTotal(HttpClientUtil.MAX_TOTAL);
        // 将每个路由基础的连接增加
        cm.setDefaultMaxPerRoute(HttpClientUtil.MAX_PER_ROUTE);
        return HttpClients.custom().setConnectionManager(cm).build();
    }

    /**
     * GET请求URL获取内容
     *
     * @param url 目标URL
     * @return 转为String类型的返回对象Entity内容
     * @throws Exception 可能抛出的异常
     * @author 姜南
     */
    public static String get(String url) throws Exception {
        HttpGet httpGet = new HttpGet(url);
        config(httpGet);
        return execute(httpGet, "UTF-8");
    }

    /**
     * POST请求URL获取内容，参数形式是Map<br>
     * 请求体的Entity类型为{@link UrlEncodedFormEntity}<br>
     *
     * @param url    目标URL
     * @param params 请求参数Map，其中key的值等于请求报文中的参数名称
     * @return 转为String类型的返回对象Entity内容
     * @throws IOException 可能抛出的异常
     * @author 姜南
     */
    public static String post(String url, Map<String, String> params)
            throws Exception {
        HttpPost httpPost = new HttpPost(url);
        config(httpPost);
        setPostParams(httpPost, params);
        return execute(httpPost, "UTF-8");
    }

    /**
     * 使用NameValuePair的方式配置POST请求体<br>
     * Entity类型为{@link UrlEncodedFormEntity}<br>
     *
     * @param httpost HTTP POST请求对象
     * @param params  请求参数Map
     * @author 姜南
     */
    private static void setPostParams(HttpPost httpost, Map<String, String> params) throws Exception {
        List<NameValuePair> nvps = new ArrayList<>();
        for (Entry<String, String> entry : params.entrySet()) {
            nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        httpost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
    }

    /**
     * 向目标地址发出HTTP POST请求<br>
     * 参数形式为JSON字符串<br>
     * 默认无请求头<br>
     * 默认ContentType为"application/json"<br>
     * 默认请求字符集为UTF-8，响应字符集为UTF-8<br>
     *
     * @param url    目标URL
     * @param params JSON字符串
     * @return 转为String类型的返回对象Entity内容
     * @throws Exception 可能抛出的异常
     * @author 姜南
     */
    public static String post(String url, String params) throws Exception {
        return post(url, params, null, "application/json");
    }

    /**
     * 向目标地址发出HTTP POST请求<br>
     * 请求报文体为{@link StringEntity}<br>
     * 可自定义ContentType<br>
     * 可自定义报文头<br>
     * 默认请求字符集为UTF-8，响应字符集为UTF-8<br>
     *
     * @param url         目标URL
     * @param params      请求内容字符串
     * @param heads       请求头Map
     * @param contentType 请求内容类型声明
     * @return 转为String类型的返回对象Entity内容
     * @throws Exception 可能抛出的异常
     * @author 姜南
     */
    public static String post(String url, String params, Map<String, String> heads, String contentType) throws Exception {
        return post(url, params, heads, contentType, "UTF-8", "UTF-8");
    }

    /**
     * 向目标地址发出HTTP POST请求<br>
     * 请求报文体为{@link StringEntity}<br>
     * 可自定义ContentType<br>
     * 可自定义报文头<br>
     * 可自定义请求体字符集<br>
     * 可自定义响应内容字符集<br>
     *
     * @param url         目标URL
     * @param params      请求内容字符串
     * @param heads       请求头Map
     * @param contentType 请求内容类型声明
     * @param reqCharset  请求内容字符集
     * @param respCharset 响应内容字符集
     * @return 转为String类型的返回对象Entity内容
     * @throws Exception 可能抛出的异常
     * @author 姜南
     */
    public static String post(String url, String params, Map<String, String> heads, String contentType, String reqCharset, String respCharset) throws Exception {
        HttpPost httpPost = new HttpPost(url);
        config(httpPost);
        addStringEntity(httpPost, params, contentType, reqCharset);
        addHeaders(httpPost, heads);
        return execute(httpPost, respCharset);
    }

    /**
     * 向HTTP POST请求对象中添加{@link StringEntity}
     *
     * @param httpPost    HTTP POST请求对象
     * @param content     请求内容字符串
     * @param contentType 请求内容类型声明
     * @param reqCharset  请求内容字符集
     */
    private static void addStringEntity(HttpPost httpPost, String content, String contentType, String reqCharset) {
        if (content != null && httpPost != null) {
            StringEntity entity = new StringEntity(content, reqCharset);
            entity.setContentEncoding("UTF-8");
            entity.setContentType(contentType);
            httpPost.setEntity(entity);
        }
    }

    /**
     * 向HTTP POST请求对象中添加HTTP请求头
     *
     * @param request HTTP请求对象
     * @param headers 请求头Map
     */
    private static void addHeaders(HttpRequestBase request, Map<String, String> headers) {
        if (headers != null && request != null) {
            for (Entry<String, String> entry : headers.entrySet()) {
                request.addHeader(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * 执行HTTP请求
     *
     * @param request     HTTP请求对象，可以是GET或POST
     * @param respCharset 响应内容字符集，用于从响应体中提取内容
     * @return 从响应体中提取的字符串形式的响应内容
     * @throws Exception 可能抛出的异常
     */
    private static String execute(HttpRequestBase request, String respCharset) throws Exception {
        try (CloseableHttpResponse response = getHttpClient().execute(request)) {
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, respCharset);
            EntityUtils.consume(entity);
            return result;
        }
    }

    /**
     * 枚举单例模式初始化客户端
     */
    private enum HttpClientHolder {

        /**
         * 实例枚举
         */
        INSTANCE;

        private CloseableHttpClient client;

        HttpClientHolder() {
            this.client = createHttpClient();
        }

        public CloseableHttpClient getClient() {
            return client;
        }

    }

}
